package com.guanyu.u2cache.aop;

import cn.hutool.core.util.ObjectUtil;
import com.guanyu.u2cache.annotation.U2Cacheable;
import com.guanyu.u2cache.cache.CacheOperate;
import com.guanyu.u2cache.confinuelife.CachedInvocation;
import com.guanyu.u2cache.enums.CacheType;
import com.guanyu.u2cache.enums.EventType;
import com.guanyu.u2cache.expression.CacheOperationExpressionEvaluator;
import com.guanyu.u2cache.expression.GeneratorTransfer;
import com.guanyu.u2cache.model.EventData;
import com.guanyu.u2cache.util.MethodUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author zhouyunjian
 * @date 2023/10/27 17:09
 **/
@Slf4j
@Aspect
@Component
public class U2CacheableAop implements ApplicationContextAware {
    @Resource
    private CacheOperate cacheOperate;
    private ApplicationContext applicationContext;
    private final GeneratorTransfer transfer = new GeneratorTransfer();
    private KeyGenerator keyGenerator = new SimpleKeyGenerator();

    @Pointcut("@annotation(com.guanyu.u2cache.annotation.U2Cacheable)")
    public void cache() {
    }


    /**
     * 缓存环绕通知：
     * 1、方法体执行前,先获取缓存信息
     * 2、方法体执行后,将结果存储到缓存当中
     * 3、如果执行异常，则不做处理，打印错误日志
     *
     * @param joinPoint
     */
    @Around("cache()")
    @SneakyThrows
    public Object around(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        U2Cacheable cacheable = AnnotationUtils.getAnnotation(signature.getMethod(), U2Cacheable.class);
        String[] eventNames = cacheable.cacheNames();
        CacheType cacheType = cacheable.cacheType();
        long expireTime = cacheable.expireTime();
        TimeUnit timeUnit = cacheable.expireUnit();
        Object[] args = joinPoint.getArgs();
        Object target = joinPoint.getTarget();
        //KeyGenerator keyGenerator = new SimpleKeyGenerator();//SpringUtil.getBean(KeyCacheConfig.keyName, KeyGenerator.class);
        Method method = MethodUtil.getSpecificmethod(joinPoint);
        String key = transfer.generateKey(new CacheOperationExpressionEvaluator(), keyGenerator, cacheable.key(), method, args, target).toString();
        Class returnType = signature.getReturnType();
        CachedInvocation cachedInvocation = CachedInvocation.builder()
                .arguments(args)
                .targetBean(target)
                .targetMethod(method)
                .returnType(returnType)
                .metaData(EventData.builder()
                                .cacheNames(eventNames)
                                .listenEventNames(cacheable.listenEventName())
                                .key(key)
                                .expiredTime(expireTime)
                                .expireUnit(timeUnit)
//                        .preLoadTime(cacheable.preLoadTime())
//                        .preLoadUnit(cacheable.preLoadUnit())
                                .eventType(EventType.load)
                                .cacheType(cacheType)
                                .build()
                )
                .build();
//        if (cacheable.continueLife() && cacheOperate.isExistKey(eventNames, key, cacheType)) {
//            applicationContext.publishEvent(cachedInvocation);
//        }
        log.debug("【{缓存查询}】进入前置name[{}]key[{}]缓存策略[{}]", eventNames, key, cacheType);
        Object result = cacheOperate.getCache(eventNames, key, cacheType, expireTime, timeUnit);
        if (ObjectUtil.isNotEmpty(result)) {
            log.debug("【{缓存查询}】name[{}]key[{}]缓存信息不为空", eventNames, key);
            return result;
        }
        try {
            result = joinPoint.proceed();
            cacheOperate.putCache(eventNames, key, cacheType, result, returnType, expireTime, timeUnit);
        } catch (Throwable e) {
            log.error("【{缓存查询}】事件name[{}]对象名[{}]缓存key[{}]处理出异常[{}]", eventNames,target.toString(), key, e.getMessage(),e);
        }
        return result;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}
